package com.asgard.crypto;

import org.bouncycastle.crypto.BufferedBlockCipher;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.engines.AESEngine;
import org.bouncycastle.crypto.generators.SCrypt;
import org.bouncycastle.crypto.modes.CBCBlockCipher;
import org.bouncycastle.crypto.paddings.BlockCipherPadding;
import org.bouncycastle.crypto.paddings.PKCS7Padding;
import org.bouncycastle.crypto.paddings.PaddedBufferedBlockCipher;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.KeyParameter;
import org.bouncycastle.crypto.params.ParametersWithIV;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.IESCipher;
import org.bouncycastle.jcajce.provider.asymmetric.util.EC5Util;
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.ECPointUtil;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECNamedCurveParameterSpec;
import org.bouncycastle.jce.spec.ECPrivateKeySpec;
import org.bouncycastle.jce.spec.ECPublicKeySpec;
import org.bouncycastle.math.ec.ECCurve;
import org.bouncycastle.math.ec.ECPoint;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;

public class ClassicCryptoService implements CryptoServiceProvider {
    static {
        if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
            Security.addProvider(new BouncyCastleProvider());
        }
    }

    // 对称加密密钥长度
    private static final int KEY_SIZE = 128 / 8;
    // 对称加密块长度
    private static final int BLOCK_SIZE = 128 / 8;
    // 非对称算法
    private static final String ASYMMETRIC_ALGO_EC = "EC";
    // 椭圆曲线名称
    private static final String EC_CURVE = "prime256v1";
    // SM3 哈希
    private static final String HASH_SHA256 = "SHA-256";
    // 签名算法
    public static final String SIG_SHA256_WITH_ECDSA = "SHA256withECDSA";
    // 椭圆曲线加密算法
    private static final String ENC_ECIES = "ECIES";

    // 随机数
    private final SecureRandom secureRandom;
    // 非对称加密生成器
    private final KeyPairGenerator generator;
    // 椭圆曲线参数
    private final ECNamedCurveParameterSpec paramSpec;
    // 椭圆曲线参数
    private final ECDomainParameters params;
    // 密钥转换
    private final KeyFactory factory;

    private volatile static ClassicCryptoService instance;

    public static ClassicCryptoService getInstance() {
        if (instance == null) {
            synchronized (ClassicCryptoService.class) {
                if (instance == null) {
                    instance = new ClassicCryptoService();
                }
            }
        }
        return instance;
    }

    public ClassicCryptoService() {
        this(new SecureRandom());
    }

    public ClassicCryptoService(SecureRandom secureRandom) {
        this.secureRandom = secureRandom;
        try {
            this.generator = KeyPairGenerator.getInstance(ASYMMETRIC_ALGO_EC, BouncyCastleProvider.PROVIDER_NAME);
            this.factory = KeyFactory.getInstance(ASYMMETRIC_ALGO_EC, BouncyCastleProvider.PROVIDER_NAME);
            this.paramSpec = ECNamedCurveTable.getParameterSpec(EC_CURVE);
            this.params = new ECDomainParameters(paramSpec.getCurve(), paramSpec.getG(), paramSpec.getN(), paramSpec.getH());
            this.generator.initialize(paramSpec, secureRandom);
        } catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidAlgorithmParameterException e) {
            // 加密参数主动设置，不会出现上述错误
            throw new RuntimeException("缺少密码库支持");
        }
    }

    @Override
    public byte[] generateKey() {
        byte[] key = new byte[KEY_SIZE];
        secureRandom.nextBytes(key);
        return key;
    }

    @Override
    public byte[] generateKey(String password, byte[] salt, boolean useKDF) {
        if (password == null) {
            throw new IllegalArgumentException("Password must be provided.");
        }

        if (!useKDF) {
            return Arrays.copyOf(hashTwice(password.getBytes(StandardCharsets.UTF_8), salt), KEY_SIZE);
        } else {
            return SCrypt.generate(password.getBytes(StandardCharsets.UTF_8), salt,
                    16384, 8, 1, KEY_SIZE);
        }
    }

    @Override
    public KeyPair generateKeyPair() {
        return generator.generateKeyPair();
    }

    @Override
    public byte[] hash(byte[]... data) {
        try {
            MessageDigest md = MessageDigest.getInstance(HASH_SHA256, BouncyCastleProvider.PROVIDER_NAME);
            for (byte[] d : data) {
                md.update(d);
            }
            return md.digest();
        } catch (NoSuchAlgorithmException | NoSuchProviderException e) {
            throw new RuntimeException("缺少SHA-256支持");
        }
    }

    @Override
    public byte[] hashTwice(byte[]... data) {
        return hash(hash(data));
    }

    @Override
    public byte[] sign(PrivateKey privateKey, byte[] data) {
        try {
            Signature sig = Signature.getInstance(SIG_SHA256_WITH_ECDSA, BouncyCastleProvider.PROVIDER_NAME);
            sig.initSign(privateKey);
            sig.update(data);
            byte[] signature = sig.sign();
            return signature;
        } catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchProviderException | SignatureException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public boolean verify(PublicKey publicKey, byte[] data, byte[] signature) {
        try {
            Signature verifier = Signature.getInstance(SIG_SHA256_WITH_ECDSA, BouncyCastleProvider.PROVIDER_NAME);
            verifier.initVerify(publicKey);
            verifier.update(data);
            boolean result = verifier.verify(signature);
            return result;
        } catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidKeyException | SignatureException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public byte[] asymmetricEncrypt(PublicKey publicKey, byte[] data) {
        try {
            IESCipher.ECIES c1 = new IESCipher.ECIES();
            c1.engineInit(Cipher.ENCRYPT_MODE, publicKey, secureRandom);
            return c1.engineDoFinal(data, 0, data.length);
        } catch (InvalidKeyException | BadPaddingException | IllegalBlockSizeException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public byte[] asymmetricDecrypt(PrivateKey privateKey, byte[] cipherText) {
        try {
            IESCipher.ECIES c1 = new IESCipher.ECIES();
            c1.engineInit(Cipher.DECRYPT_MODE, privateKey, secureRandom);
            return c1.engineDoFinal(cipherText, 0, cipherText.length);
        } catch (InvalidKeyException | BadPaddingException | IllegalBlockSizeException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public byte[] symmetricEncrypt(byte[] key, byte[] data) {
        if (key.length != KEY_SIZE) {
            throw new RuntimeException("对称加密密钥长度不正确，期望值为16字节");
        }

        // iv 保证对称加密密钥在多次使用下的安全性，类似盐值
        byte[] iv = new byte[BLOCK_SIZE];
        secureRandom.nextBytes(iv);

        // AES/CBC/PKCS7Padding
        BlockCipherPadding padding = new PKCS7Padding();
        BufferedBlockCipher cipher = new PaddedBufferedBlockCipher(
                new CBCBlockCipher(new AESEngine()), padding);
        cipher.init(true, new ParametersWithIV(new KeyParameter(key), iv));

        byte[] buffer = new byte[cipher.getOutputSize(data.length)];
        int len = cipher.processBytes(data, 0, data.length, buffer, 0);
        try {
            len += cipher.doFinal(buffer, len);
        } catch (InvalidCipherTextException e) {
            throw new RuntimeException(e);
        }

        // 将 IV 放在密文最前面
        byte[] output = new byte[len + BLOCK_SIZE];
        System.arraycopy(iv, 0, output, 0, BLOCK_SIZE);
        System.arraycopy(buffer, 0, output, BLOCK_SIZE, len);
        return output;
    }

    @Override
    public byte[] symmetricDecrypt(byte[] key, byte[] cipherText) {
        if (key.length != KEY_SIZE) {
            throw new RuntimeException("对称加密密钥长度不正确，期望值为16字节");
        }

        // 取出 IV
        byte[] iv = new byte[BLOCK_SIZE];
        System.arraycopy(cipherText, 0, iv, 0, BLOCK_SIZE);
        byte[] cipherTextWithoutIV = new byte[cipherText.length - BLOCK_SIZE];
        System.arraycopy(cipherText, BLOCK_SIZE, cipherTextWithoutIV, 0, cipherText.length - BLOCK_SIZE);

        BlockCipherPadding padding = new PKCS7Padding();
        BufferedBlockCipher cipher = new PaddedBufferedBlockCipher(
                new CBCBlockCipher(new AESEngine()), padding);
        cipher.init(false, new ParametersWithIV(new KeyParameter(key), iv));

        byte[] buffer = new byte[cipher.getOutputSize(cipherText.length - BLOCK_SIZE)];
        int len = cipher.processBytes(cipherText, BLOCK_SIZE, cipherText.length - BLOCK_SIZE, buffer, 0);
        try {
            len += cipher.doFinal(buffer, len);
        } catch (InvalidCipherTextException e) {
            e.printStackTrace();
            throw new RuntimeException(e);
        }

        return Arrays.copyOfRange(buffer, 0, len);
    }

    @Override
    public PrivateKey parsePrivateKey(byte[] privateKeyBytes) {
        try {
            // 直接是 32 bytes 随机数
            if (privateKeyBytes.length == 32) {
                BigInteger d = new BigInteger(1, privateKeyBytes);
                return factory.generatePrivate(new ECPrivateKeySpec(d, paramSpec));
            }

            // PKCS8 格式
            return factory.generatePrivate(new PKCS8EncodedKeySpec(privateKeyBytes));
        } catch (InvalidKeySpecException e) {
            throw new RuntimeException("invalid private key bytes");
        }
    }

    @Override
    public PublicKey parsePublicKey(byte[] publicKeyBytes) {
        try {
            // 无压缩，1 + X (32 bytes) + Y (32 bytes)
            if (publicKeyBytes.length == 65) {
                ECCurve curve = paramSpec.getCurve();
                java.security.spec.EllipticCurve ellipticCurve = EC5Util.convertCurve(curve, paramSpec.getSeed());
                java.security.spec.ECPoint point = ECPointUtil.decodePoint(ellipticCurve, publicKeyBytes);
                java.security.spec.ECParameterSpec params = EC5Util.convertSpec(ellipticCurve, paramSpec);
                java.security.spec.ECPublicKeySpec keySpec = new java.security.spec.ECPublicKeySpec(point, params);
                return factory.generatePublic(keySpec);
            }

            // 有压缩，1 + X (32 bytes)
            if (publicKeyBytes.length == 33) {
                ECCurve curve = paramSpec.getCurve();
                ECPoint q = curve.decodePoint(publicKeyBytes);
                return factory.generatePublic(new ECPublicKeySpec(q, paramSpec));
            }

            // 默认 X509 格式压缩公钥
            return factory.generatePublic(new X509EncodedKeySpec(publicKeyBytes));
        } catch (InvalidKeySpecException e) {
            throw new RuntimeException("invalid public key bytes");
        }
    }

    @Override
    public PublicKey generatePublicKey(PrivateKey privateKey) throws InvalidKeySpecException {
        BCECPrivateKey priKey = (BCECPrivateKey) privateKey;
        BigInteger d = priKey.getD();
        ECPoint G = params.getG();
        ECPoint Q = G.multiply(d);
        return factory.generatePublic(new ECPublicKeySpec(Q, paramSpec));
    }
}
